/*
 * 文件名：sd_server.cpp
 * 功能描述：SD卡文件服务器和Web界面
 */
#include "sd_server.h"
#include "esp_task_wdt.h"
#include "bsp_sdcard.h"
// #include <SD.h>

#ifdef FILE_READ
#undef FILE_READ
#endif

#ifdef FILE_WRITE
#undef FILE_WRITE
#endif
#include <SPI.h>
#include <SdFat.h>
#include <sdios.h>
#define FS_NO_GLOBALS
#include "sdfat_fs_wrapper.h"

const char *ssid = "LingS";
const char *password = "50505050";

AsyncWebServer server1(80);
AsyncWebServer server2(81);
AsyncWebServer server3(82);

// HTML form for the web server with AJAX
const char step_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>Stepper Motor Control</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      background-color: #f0f0f0;
      margin: 0;
      padding: 0;
    }
    .container {
      max-width: 600px;
      margin: auto;
      padding: 20px;
      background-color: #ffffff;
      box-shadow: 0 0 10px rgba(0,0,0,0.1);
      margin-top: 50px;
    }
    h2 {
      color: #333333;
    }
    textarea {
      width: 80%;
      height: 100px;
      padding: 10px;
      margin: 10px 0;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    input[type="button"] {
      background-color: #4CAF50;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    input[type="button"]:hover {
      background-color: #45a049;
    }
    #response {
      margin-top: 20px;
    }
  </style>
  <script>
    function sendData() {
      var coordinates = document.getElementById("coordinates").value;
      var xhr = new XMLHttpRequest();
      xhr.open("POST", "/moveto/complete", true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          document.getElementById("response").innerHTML = xhr.responseText;
        }
      };
      xhr.send("coordinates=" + encodeURIComponent(coordinates));
    }
  </script>
</head>
<body>
  <div class="container">
    <h2>Stepper Motor Control</h2>
    <form id="controlForm" onsubmit="sendData(); return false;">
      <label for="coordinates">Coordinates (format: x1,y1 x2,y2 ...):</label><br>
      <textarea id="coordinates" name="coordinates" placeholder="Enter coordinates..."></textarea><br><br>
      <input type="button" value="Move To" onclick="sendData()">
    </form>
    <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRg4EAjjkxINkfZ08wswnhdc9q8bLQxnR0tIQ&s" alt="Stepper Motor">
    <div id="response"></div>
  </div>
</body>
</html>
)rawliteral";

// HTML form for file upload, download and listing
const char file_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>File Upload, Download and List</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    /* 样式代码 */
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      background-color: #f0f0f0;
      margin: 0;
      padding: 0;
    }
    .container {
      max-width: 600px;
      margin: auto;
      padding: 20px;
      background-color: #ffffff;
      box-shadow: 0 0 10px rgba(0,0,0,0.1);
      margin-top: 50px;
    }
    h2 {
      color: #333333;
    }
    input[type="file"], input[type="text"] {
      margin: 10px 0;
      padding: 10px;
      width: calc(100% - 20px);
      box-sizing: border-box;
    }
    input[type="button"] {
      background-color: #4CAF50;
      color: white;
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
    }
    input[type="button"]:hover {
      background-color: #45a049;
    }
    table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 20px;
    }
    th, td {
      padding: 10px;
      border: 1px solid #ccc;
      text-align: left;
    }
    th {
      background-color: #f2f2f2;
    }
    .folder {
      cursor: pointer;
      font-weight: bold;
    }
    .folder-content {
      display: none;
    }
  </style>
  <script>
    // 上传文件
    function uploadFile() {
      var fileInput = document.getElementById("fileInput");
      var file = fileInput.files[0];
      var path = document.getElementById("path").value;
      var formData = new FormData();
      formData.append("file", file);
      formData.append("path", path);

      var xhr = new XMLHttpRequest();
      xhr.open("POST", "/file/upload?path=" + encodeURIComponent(path), true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          document.getElementById("uploadResponse").innerHTML = xhr.responseText;
          listFiles();  // 上传后刷新文件列表
        }
      };
      xhr.send(formData);
    }

    // 下载文件
    function downloadFile(filePath) {
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "/download?filePath=" + encodeURIComponent(filePath), true);
      xhr.responseType = 'blob';
      xhr.onload = function() {
        if (xhr.status === 200) {
          var link = document.createElement('a');
          link.href = window.URL.createObjectURL(xhr.response);
          link.download = filePath.split('/').pop();  // 从路径中提取文件名
          link.click();
          document.getElementById("downloadResponse").innerHTML = "File Downloaded Successfully";
        } else {
          document.getElementById("downloadResponse").innerHTML = "File Not Found";
        }
      };
      xhr.onerror = function() {
        document.getElementById("downloadResponse").innerHTML = "Download Error";
      };
      xhr.send();
    }

    // 删除文件
    function deleteFile(filePath) {
      var xhr = new XMLHttpRequest();
      xhr.open("DELETE", "/file/delete?filePath=" + encodeURIComponent(filePath), true);
      xhr.onload = function() {
        if (xhr.status === 200) {
          document.getElementById("deleteResponse").innerHTML = "File Deleted Successfully";
          listFiles();  // 删除后刷新文件列表
        } else {
          document.getElementById("deleteResponse").innerHTML = "File Not Found";
        }
      };
      xhr.onerror = function() {
        document.getElementById("deleteResponse").innerHTML = "Delete Error";
      };
      xhr.send();
    }

    // 列出文件
    function listFiles() {
      var path = document.getElementById("path").value;
      var xhr = new XMLHttpRequest();
      xhr.open("GET", "/list?path=" + encodeURIComponent(path), true);
      xhr.onreadystatechange = function() {
        if (xhr.readyState == 4 && xhr.status == 200) {
          var fileList = JSON.parse(xhr.responseText);
          var table = '<table><tr><th>Name</th><th>Size</th><th>Time</th><th>Rows</th><th>Actions</th></tr>';
          fileList.forEach(function(file) {
            if (file.isDirectory) {
              var folderId = 'folder-' + file.name.replace(/\W/g, '');
              table += '<tr class="folder" onclick="toggleFolder(\'' + folderId + '\')"><td colspan="5">' + file.name + '</td></tr>';
              table += '<tbody id="' + folderId + '" class="folder-content">';
              file.files.forEach(function(subFile) {
                table += '<tr><td>' + subFile.name + '</td><td>' + subFile.size + ' bytes</td>';
                table += '<td>' + subFile.time + '</td><td>' + subFile.rows + '</td>';
                table += '<td><button onclick="downloadFile(\'' + subFile.path + '\')">Download</button>';
                table += '<button onclick="deleteFile(\'' + subFile.path + '\')">Delete</button></td></tr>';
              });
              table += '</tbody>';
            } else {
              table += '<tr><td>' + file.name + '</td><td>' + file.size + ' bytes</td>';
              table += '<td>' + file.time + '</td><td>' + file.rows + '</td>';
              table += '<td><button onclick="downloadFile(\'' + file.path + '\')">Download</button>';
              table += '<button onclick="deleteFile(\'' + file.path + '\')">Delete</button></td></tr>';
            }
          });
          table += '</table>';
          document.getElementById("fileList").innerHTML = table;
        }
      };
      xhr.send();
    }

    // 页面加载时列出文件
    window.onload = function() {
      listFiles();
    };

    // 切换文件夹显示状态
    function toggleFolder(folderId) {
      var content = document.getElementById(folderId);
      if (content.style.display === "none") {
        content.style.display = "table-row-group";
      } else {
        content.style.display = "none";
      }
    }
  </script>
</head>
<body>
  <div class="container">
    <h2>File Upload</h2>
    <input type="file" id="fileInput"><br><br>
    <input type="text" id="path" name="path" placeholder="Enter folder path..."><br><br>
    <input type="button" value="Upload File" onclick="uploadFile()">
    <div id="uploadResponse"></div>
  </div>
  <div class="container">
    <h2>File Download and Delete Result</h2>
    <div id="downloadResponse"></div>
    <div id="deleteResponse"></div>
  </div>
  <div class="container">
    <h2>File List</h2>
    <div id="fileList"></div>
  </div>
</body>
</html>
)rawliteral";

//-----------------------------------------------------------------
// SDFat对象
// SdFile file;
// SdFile dir;
// SdFat SD;

// char pwd_path[] = "/";
// const uint16_t NAME_LENGTH = 50;
// const uint16_t LINE_LENGTH = 50;
fs::FS sdfilesys = fs::FS(fs::FSImplPtr(new SdFat32FSImpl(sd)));
// 处理步骤页面的根URL请求
void handleStepRoot(AsyncWebServerRequest *request)
{
  // 发送步骤页面的HTML内容给客户端
  request->send(200, "text/html", step_html);
}

// 处理移动到指定坐标的请求
void handleMoveTo(AsyncWebServerRequest *request)
{
  // 目前尚未实现，需根据具体需求添加代码
}

// 处理文件上传和下载页面的根URL请求
void handleFileRoot(AsyncWebServerRequest *request)
{
  // 发送文件上传和下载页面的HTML内容给客户端
  request->send(200, "text/html", file_html);
}

// 处理文件上传请求
void handleFileUpload(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final)
{
  // 获取路径参数
  String path = request->arg("path");
  Serial.println(path + "?"); // 打印路径参数

  // 如果路径不以斜杠开头，则添加斜杠
  if (path[0] != '/')
  {
    path = "/" + path;
  }
  
  // 如果路径不以斜杠结尾，则添加斜杠
  if (!path.endsWith("/"))
  {
    path += "/";
  }
  
  // 合并路径和文件名
  filename = path + filename;

  // 如果这是文件上传的开始
  if (!index)
  {
    Serial.printf("上传开始: %s\n", filename.c_str()); // 打印文件名
    // 尝试打开文件以进行写入，如果失败则返回错误
    if (!file.open(filename.c_str(), O_CREAT | O_WRITE | O_TRUNC))
    {
      Serial.printf("无法打开文件 %s 进行写入\n", filename.c_str());
      request->send(500, "text/plain", "无法打开文件进行写入");
      return;
    }
  }

  // 如果有数据需要写入文件
  if (len)
  {
    file.write(data, len); // 写入数据到文件
    Serial.printf("正在写入文件: %s, 写入字节数: %d\n", filename.c_str(), len); // 打印写入的字节数
  }

  // 如果这是文件上传的最后一部分
  if (final)
  {
    file.close(); // 关闭文件
    Serial.printf("上传结束: %s, 总字节数: %u\n", filename.c_str(), index + len); // 打印总字节数
    request->send(200, "text/plain", "文件上传成功"); // 向客户端发送成功消息
  }

  // 重置目录，准备处理下一个文件
  dir.rewind(); 
  dir.close();
}

// 处理文件下载请求
void handleFileDownload(AsyncWebServerRequest *request)
{
  // 获取文件路径参数
  String filePath = request->arg("filePath");

  Serial.println(filePath); // 打印文件路径
  // 尝试打开文件以进行读取，如果失败则返回错误
  if (!file.open(filePath.c_str(), O_RDONLY))
  {
    request->send(404, "text/plain", "文件未找到");
    return;
  }
  
  // 发送文件内容作为二进制流给客户端
  request->send(sdfilesys, filePath, "application/octet-stream");
  file.close(); // 关闭文件
}

void handleFileDelete(AsyncWebServerRequest *request)
{
  String filePath = request->arg("filePath"); // 获取文件路径参数

  if (sd.remove(filePath.c_str()))
  {
    request->send(200, "text/plain", "File Deleted Successfully");
  }
  else
  {
    request->send(500, "text/plain", "Failed to delete file");
  }
  dir.rewind(); // 重置
  dir.close();
}

// 获取文件的最后修改时间并返回格式化的时间字符串
String getFileTime(SdFile &file)
{
  dir_t dir;
  // 获取文件的目录条目，如果失败则返回"N/A"
  if (!file.dirEntry(&dir))
  {
    return String("N/A");
  }

  // 提取文件的最后写入日期和时间
  uint16_t date = dir.lastWriteDate;
  uint16_t time = dir.lastWriteTime;

  // 将日期和时间解析为具体的年、月、日、时、分、秒
  uint16_t year = FAT_YEAR(date);
  uint8_t month = FAT_MONTH(date);
  uint8_t day = FAT_DAY(date);
  uint8_t hour = FAT_HOUR(time);
  uint8_t minute = FAT_MINUTE(time);
  uint8_t second = FAT_SECOND(time);

  // 格式化时间字符串
  char buffer[20];
  sprintf(buffer, "%04u-%02u-%02u %02u:%02u:%02u", year, month, day, hour, minute, second);
  return String(buffer); // 返回时间字符串
}

// 列出子目录中的所有文件
void listSubDirectory(SdFile &list_dir, String &output)
{
  esp_task_wdt_delete(NULL); // 从看门狗任务监督中删除当前任务

  SdFile list_file;
  bool firstFile = true; // 标记是否是第一个文件

  char dirname[NAME_LENGTH];
  list_dir.getName(dirname, NAME_LENGTH); // 获取子目录的名称

  // 打开下一个文件或子目录
  while (list_file.openNext(&list_dir, O_RDONLY))
  {
    // 如果是第一个文件，不添加逗号分隔符
    if (firstFile)
    {
      firstFile = false;
    }
    else
    {
      output += ","; // 添加逗号分隔符
    }
    char filename[NAME_LENGTH];
    list_file.getName(filename, NAME_LENGTH); // 获取文件名

    String fileTime = getFileTime(list_file); // 获取文件的最后修改时间
    int fileLines = countLines(&list_file);   // 计算文件的行数
    String fullPath = String(dirname) + "/" + String(filename); // 生成文件的完整路径
    // 将文件的相关信息添加到输出字符串中
    output += "{\"name\":\"" + String(filename) + "\", \"size\":" + String(list_file.fileSize()) + ", \"time\":\"" + fileTime + "\", \"rows\":" + String(fileLines) + ", \"isDirectory\":false, \"path\":\"" + fullPath + "\"}";

    list_file.close();       // 关闭文件
    esp_task_wdt_reset();    // 重置任务看门狗
    yield();                 // 让出CPU控制权
  }
  list_dir.rewind();         // 重置目录，准备下次读取
}

// 列出目录中的所有文件和子目录
void listDirectory(SdFile &list_dir, String &output)
{
  esp_task_wdt_delete(NULL); // 从看门狗任务监督中删除当前任务

  SdFile list_file;
  bool firstFile = true; // 标记是否是第一个文件或目录

  char dirname[NAME_LENGTH];
  list_dir.getName(dirname, NAME_LENGTH); // 获取目录名称

  // 打开下一个文件或子目录
  while (list_file.openNext(&list_dir, O_RDONLY))
  {
    // 如果是第一个文件或目录，不添加逗号分隔符
    if (firstFile)
    {
      firstFile = false;
    }
    else
    {
      output += ","; // 添加逗号分隔符
    }
    char filename[NAME_LENGTH];
    list_file.getName(filename, NAME_LENGTH); // 获取文件或目录名称
    // 检查是否是目录
    if (list_file.isDir())
    {
      String subOutput = "[";

      // 遍历并列出子目录中的文件
      listSubDirectory(list_file, subOutput);

      subOutput += "]";
      // 将子目录信息添加到输出字符串中
      output += "{\"name\":\"" + String(filename) + "\", \"isDirectory\":true, \"path\":\"" + String(dirname) + "\", \"files\":" + subOutput + "}";
    }
    else
    {
      String fileTime = getFileTime(list_file); // 获取文件的最后修改时间
      int fileLines = countLines(&list_file);   // 计算文件的行数
      String fullPath = String(dirname) + "/" + String(filename); // 生成文件的完整路径
      // 将文件的相关信息添加到输出字符串中
      output += "{\"name\":\"" + String(filename) + "\", \"size\":" + String(list_file.fileSize()) + ", \"time\":\"" + fileTime + "\", \"rows\":" + String(fileLines) + ", \"isDirectory\":false, \"path\":\"" + fullPath + "\"}";
    }
    list_file.close();       // 关闭文件或目录
    esp_task_wdt_reset();    // 重置任务看门狗
    yield();                 // 让出CPU控制权
  }
  list_dir.rewind();         // 重置目录，准备下次读取
}

// 处理客户端请求的文件列表
void handleFileList(AsyncWebServerRequest *request)
{
  SdFile list_dir;
  list_dir.open(pwd_path);   // 打开当前工作目录（pwd_path）

  String output = "[";       // 准备一个空的JSON数组字符串
  listDirectory(list_dir, output); // 列出目录中的所有文件和子目录
  output += "]";             // 关闭JSON数组

  // 将生成的JSON数组作为响应发送给客户端
  request->send(200, "application/json", output);

  list_dir.close();          // 关闭目录
}


void setServer()
{
  SPI.begin(SD_CLK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN); // CLK, MISO, MOSI, CS
  if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(16)))
  {
    sd.initErrorHalt();
  }

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED)
  {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
  Serial.print("IP 地址: ");
  Serial.println(WiFi.localIP());

  server1.on("/moveto", HTTP_GET, handleStepRoot);
  server1.on("/moveto/complete", HTTP_POST, handleMoveTo);

  server2.on("/file", HTTP_GET, handleFileRoot);
  server2.on("/download", HTTP_GET, handleFileDownload);
  server2.on("/file/upload", HTTP_POST, [](AsyncWebServerRequest *request)
             {
               // empty, handled in file upload
             },
             handleFileUpload);
  server2.on("/file/delete", HTTP_DELETE, handleFileDelete);
  server2.on("/list", HTTP_GET, handleFileList);

  server3.on("/hello", HTTP_GET, [](AsyncWebServerRequest *request)
             { request->send(200, "text/plain", "Hello from server 3"); });

  server2.onNotFound([](AsyncWebServerRequest *request)
                     {
    Serial.printf("NOT_FOUND: ");
    if(request->method() == HTTP_GET) Serial.printf("GET");
    else if(request->method() == HTTP_POST) Serial.printf("POST");
    else if(request->method() == HTTP_DELETE) Serial.printf("DELETE");
    else if(request->method() == HTTP_PUT) Serial.printf("PUT");
    else if(request->method() == HTTP_PATCH) Serial.printf("PATCH");
    else if(request->method() == HTTP_HEAD) Serial.printf("HEAD");
    else if(request->method() == HTTP_OPTIONS) Serial.printf("OPTIONS");
    else Serial.printf("UNKNOWN");
    Serial.printf(" http://%s%s\n", request->host().c_str(), request->url().c_str());

    if(request->contentLength()){
      Serial.printf("_CONTENT_TYPE: %s\n", request->contentType().c_str());
      Serial.printf("_CONTENT_LENGTH: %u\n", request->contentLength());
    }

    int headers = request->headers();
    for(int i = 0; i < headers; i++){
        const  AsyncWebHeader* h = request->getHeader(i);
      Serial.printf("_HEADER[%s]: %s\n", h->name().c_str(), h->value().c_str());
    }

    int params = request->params();
    for(int i = 0; i < params; i++){
        const AsyncWebParameter* p = request->getParam(i);
      if(p->isFile()){
        Serial.printf("_FILE[%s]: %s, size: %u\n", p->name().c_str(), p->value().c_str(), p->size());
      } else if(p->isPost()){
        Serial.printf("_POST[%s]: %s\n", p->name().c_str(), p->value().c_str());
      } else {
        Serial.printf("_GET[%s]: %s\n", p->name().c_str(), p->value().c_str());
      }
    }

    request->send(404); });
  esp_task_wdt_delete(NULL); // Delete current task from WDT supervision
  server1.begin();
  server2.begin();
  server3.begin();
}